/******************************************************************************************************************
* Description:
*
* This superclass defines filter framework that defines a filter in terms of any number the input and output
* ports. All filters must be defined in terms of this framework - that is, filters must extend this class
* in order to be considered valid system filters. Filters run as standalone threads.
*
* @author Team 10
******************************************************************************************************************/

import java.io.PipedInputStream;
import java.io.PipedOutputStream;


public class FilterFramework2 extends Thread
{
	// Define filter input and output ports
	private PipedInputStream[] InputReadPorts = null;
	private PipedOutputStream[] OutputWritePorts = null;
	
	// Size of the pipe internal filter
	private static final int PIPE_SIZE = 100000;

	// The following reference to a filter is used because java pipes are able to reliably
	// detect broken pipes on the input ports of the filter. This variable will point to
	// the previous filters in the network and when they die, we know that it has closed its
	// output pipe and will send no more data.
	
	private FilterFramework2[] InputFilters;
	
	// Constructor 
	// Takes the number of input ports and output ports. Initializes the pipes.
	public FilterFramework2(int inputCount,int outputCount)
	{
		InputReadPorts=new PipedInputStream[inputCount];
		for (int i = 0; i < inputCount; i++) {
			InputReadPorts[i]=new PipedInputStream(PIPE_SIZE);
			
		}
		
		OutputWritePorts=new PipedOutputStream[outputCount];
		for (int i = 0; i < outputCount; i++) {
			OutputWritePorts[i]=new PipedOutputStream();
		}
		InputFilters=new FilterFramework2[inputCount];
		
		System.out.println("Filter " + this.toString() + " created with intput " + inputCount +
				" output "+ outputCount);
		
	}
	

	// InnerClass:: EndOfStreamExeception
	// Purpose: This exception shows that a filter has stopped writing

	class EndOfStreamException extends Exception {

		EndOfStreamException () { super(); }

		EndOfStreamException(String s) { super(s); }

	} // class

	// Connect method
	// This method connects filters to each other. All connections are
	// through the inputport of each filter. That is each filter's inputport is
	// connected to another filter's output port through this method.
	
	void Connect( FilterFramework2 Filter, int inputIndex, int outputIndex )
	{
		try
		{
			// Connect this filter's input to the upstream pipe's output stream
			InputReadPorts[inputIndex].connect( Filter.OutputWritePorts[outputIndex]);
			InputFilters[inputIndex]=Filter;

		} // try

		catch( Exception Error )
		{
			System.out.println( "\n" + this.getName() + " FilterFramework error connecting::"+ Error );
		} // catch

	} // Connect

	// ReadFilterInputPort
	// This method reads data from the input port one byte at a time.
	byte ReadFilterInputPort(int inputPort) throws EndOfStreamException
	{
		byte datum = 0;

		/***********************************************************************
		* Since delays are possible on upstream filters, we first wait until
		* there is data available on the input port. We check,... if no data is
		* available on the input port we wait for a quarter of a second and check
		* again. Note there is no timeout enforced here at all and if upstream
		* filters are deadlocked, then this can result in infinite waits in this
		* loop. It is necessary to check to see if we are at the end of stream
		* in the wait loop because it is possible that the upstream filter completes
		* while we are waiting. If this happens and we do not check for the end of
		* stream, then we could wait forever on an upstream pipe that is long gone.
		* Unfortunately Java pipes do not throw exceptions when the input pipe is
		* broken.
		***********************************************************************/

		try
		{
			while (InputReadPorts[inputPort].available()==0 )
			{
				if (EndOfInputStream(inputPort))
				{
					throw new EndOfStreamException("End of input stream reached");

				} //if

				sleep(250);

			} // while

		} // try

		catch( EndOfStreamException Error )
		{
			throw Error;

		} // catch

		catch( Exception Error )
		{
			System.out.println( "\n" + this.getName() + " Error in read port wait loop::" + Error );

		} // catch

		/***********************************************************************
		* If at least one byte of data is available on the input
		* pipe we can read it. We read and write one byte to and from ports.
		***********************************************************************/

		try
		{
			datum = (byte)InputReadPorts[inputPort].read();
			return datum;

		} // try

		catch( Exception Error )
		{
			System.out.println( "\n" + this.getName() + " Pipe read error::" + Error );
			return datum;

		} // catch

	} // ReadFilterPort

	// WriteFilterOutputPort Method 
	// This method writes data to the output port one byte at a time.
	
	void WriteFilterOutputPort(byte datum,int outputPort)
	{
		try
		{
            OutputWritePorts[outputPort].write((int) datum );
		   	OutputWritePorts[outputPort].flush();

		} // try

		catch( Exception Error )
		{
			System.out.println("\n" + " " +  this.getName() + " Pipe write error::" + Error );

		} // catch

		return;

	} // WriteFilterPort
	

	// EndOfInputStream Method
	// This method is used within this framework which is why it is private
	// It returns a true when there is no more data to read on the input port of
	// the instance filter. What it really does is to check if the upstream filter
	// is still alive. This is done because Java does not reliably handle broken
	// input pipes and will often continue to read (junk) from a broken input pipe.
	private boolean EndOfInputStream(int inputPort)
	{
		if (InputFilters[inputPort].isAlive())
		{
			return false;

		} else {

			return true;

		} // if

	} // EndOfInputStream

	
	// CONCRETE METHOD:: ClosePorts Method
	// This method is used to close the input port
	// filter. It is important that filters close their ports before the filter
	// thread exits.
	
	void CloseInputPorts(int inputPort)
	{
		try
		{
			InputReadPorts[inputPort].close();
		}
		catch( Exception Error )
		{
			System.out.println( "\n" + this.getName() + " ClosePorts error::" + Error );

		} // catch

	} // ClosePorts
	
	// CloseOutputPorts Method
	// Closes the specified port. 
	void CloseOutputPorts(int outputPort)
	{
		try
		{
			OutputWritePorts[outputPort].close();
		}
		catch( Exception Error )
		{
			System.out.println( "\n" + this.getName() + " ClosePorts error::" + Error );

		} // catch

	}

	
	// run Method
	// Purpose: This is actually an abstract method defined by Thread. It is called
	// when the thread is started by calling the Thread.start() method. In this
	// case, the run() method should be overridden by the filter programmer using
	//this framework superclass
	public void run() 
    {
		// The run method should be overridden by the subordinate class. Please
		// see the example applications provided for more details.

	} // run

} // FilterFramework2 class



